<?php
	/**
	  * @package	MyDebuger
	  * @name		MyLoader
	  * @brief		Clase para cargar ficheros de clases PHP
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2009-12-20
	  * @version	0.1.4
  	**/

	final class MyLoader extends MyDebuger implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 1;
		/* numero de revision de la clase */
		private static $revision		= 4;

		/* nombre de las clases y direccion a sus ficheros */
		private $fileClasses			= Array();
		/* fichero de configuraciones */
		private $configFile			= False;
		/* bandera para saber si se cargaron los datos */
		private $datosCargados		= False;
		/* atributo temporal donde cargamos el fichero para realizar verificaciones */
		private $tempClassData		= Null;

		public function __construct($debug = False, $fileName = 'myloader.config') {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__, 086);
			/* habilitamos el registro en un fichero */
			$this->enableLog(__CLASS__ . '.log');
			/* almacenamos el nombre del fichero */
			$this->configFile = $fileName;
			/* cargamos los datos */
			$this->loadDataFromFile();
		}

		public static final function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		public function loadClass($className = Null) {
			try {
				try {
					/* realizamos algunas verfificaciones */
					$this->verificarClaseImportar($className);
					/* agregamos el fichero de la clase */
					$this->loadClassFile($className);
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		public static function fatalErrors($buffer) {
			/* verificamos si recibimos una cadena con error */
			if (ereg("(error</b>:)(.+)(<br)", $buffer, $regs)) {
				/* armamos la cadena nueva */
				$stringText = str_replace(
					/* reemplazamos los carateres <, > */
					Array('<', '>'),
					/* por su codigo HTML */
					Array('&lt;', '&gt;'),
					/* agregamos el color rojo al texto eliminando el enlace inicial */
					'|font style=\'color:#c00;\'|' . (preg_replace("/<.*?>/","", $regs[2])) . '|/font|'
				);
				/* retornamos la cadena formateada */
				return '<code>' . str_replace(
					/* remplazamos los style */
					Array('|font', '|/font|', '\'|'),
					/* con su codigo correcto */
					Array('<font', '</font>', '\'>'),
					/* de la cadena */
					$stringText
				/* finalizamos la cadena */
				) . '</code>';
			}
			/* retornamos el buffer como esta */
			return $buffer;
		}

		private function verificarClaseImportar($className, $tipo = 'clase') {
			/* mostramos un mensaje */
			$this->show('Verificando existencia de la ' . $tipo . ' ' . $this->classFormat($className) . '..', False);
			/* verificamos si especifico un nombre de clase */
			if ($className === Null)
				/* salimos con una excepcion */
				$this->newException('No se especifico el nombre de la ' . $tipo . ' a cargar');
			/* verificamos si tenemos la clase o interfaz */
			if (!array_key_exists($className, $this->fileClasses) && !class_exists($className) && !interface_exists($className))
				/* salimos con una excepcion */
				$this->newException('La ' . $tipo . ' ' . $this->classFormat($className) . ' no se especifico en las configuraciones');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function loadClassFile($className) {
			/* verificamos las interfaces de la clase */
			$this->verificarInterfacesImportar($className);
			/* verificamos el padre de la clase */
			$this->verificarPadre($className);
			/* importamos la clase */
			$this->importarClase($className);
		}

		private function verificarInterfacesImportar($className) {
			/* verificamos si tiene interfaces */
			if (!isset($this->fileClasses[$className]['interfaces']))
				/* retornamos false */
				return False;
			/* recorremos las interfaces */
			foreach ($this->fileClasses[$className]['interfaces'] AS $interfaz) {
				/* verificamos si la interfaz existe */
				$this->verificarClaseImportar($interfaz, 'interfaz');
				/* cargamos la interfaz */
				$this->loadClassFile($interfaz);
			}
		}

		private function verificarPadre($className) {
			/* verificamos si tiene padre */
			if (!isset($this->fileClasses[$className]['parent']))
				/* retornamos false */
				return False;
			/* verificamos si existe la clase padre */
			$this->verificarClaseImportar($this->fileClasses[$className]['parent']);
			/* cargamos la clase padre */
			$this->loadClassFile($this->fileClasses[$className]['parent']);
		}

		private function importarClase($className) {
			/* mostramos un mensaje */
			$this->show('Importando la' . (
				/* verificamos si es una interfaz */
				isset($this->fileClasses[$className]['isInterface']) && $this->fileClasses[$className]['isInterface'] == 'True' ?
				/* mostramos el texto 'interfaz' */
				' interfaz ' :
				/* mostramos el texto 'clase' */
				' clase '
			/* agregamos el nombre de la clase */
			) . $this->classFormat($className) . '..', False);
			/* verificamos si la clase o interfaz ya esta cargada */
			if (!interface_exists($className) && !class_exists($className)) {
				/* capturamos los errores fatales */
				ob_start('MyLoader::fatalErrors');
				/* ejecutamos la importacion */
				eval('?>' . file_get_contents($this->fileClasses[$className]['filePath']));
				/* finalizamos la captura de errores */
				ob_end_flush();
			} else
				/* mostramos un mensaje */
				$this->show('La' . (
				/* verificamos si es una interfaz */
				isset($this->fileClasses[$className]['isInterface']) && $this->fileClasses[$className]['isInterface'] == 'True' ?
				/* mostramos el texto 'interfaz' */
				' interfaz ' :
				/* mostramos el texto 'clase' */
				' clase '
			/* agregamos el final del texto */
			) . 'ya esta cargada..', False);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function loadDataFromFile() {
			try {
				try {
					/* verificamos que exista la clase necesaria */
					$this->verificarClase('MyTools');
					/* verificamos el archivo */
					$this->verificarFichero();
					/* cargamos los datos del fichero */
					$this->cargarDatosFichero();
					/* modificamos la bandera */
					$this->datosCargados = True;
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$this->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$this->show($e->getMensaje());
			}
		}

		private function verificarFichero() {
			/* mostramos un mensaje */
			$this->show('Verificando fichero de configuraciones ' . $this->fileFormat($this->configFile) . '..', False);
			/* verificamos si el fichero existe */
			if (!file_exists($this->configFile))
				/* salimos con una excepcion */
				$this->newException('El fichero no existe');
			/* verificamos si podemos leer el fichero */
			if (!is_readable($this->configFile))
				/* salimos con una excepcion */
				$this->newException('No se tienen permisos de lectura sobre el fichero');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function cargarDatosFichero() {
			/* abrimos el fichero */
			$this->abrirFichero();
			/* limpiamos el contenido del fichero */
			$this->limpiarFichero();
			/* verificamos que el formato del fichero sea correcto */
			$this->verificarFormatoFichero();
			/* procesamos el fichero */
			$this->procesarFichero();
		}

		private function abrirFichero() {
			/* obtenemos los datos del fichero */
			$this->configFile = file_get_contents($this->configFile);
		}

		private function limpiarFichero() {
			/* eliminamos los comentarios del fichero */
			$this->configFile = preg_replace('/\/\*(.*?)\*\//si', '', $this->configFile);
		}

		private function verificarFormatoFichero() {
			/* mostramos un mensaje */
			$this->show('Validando formato del fichero..', False);
			/* verificamos si el formato del fichero es correcto */
			if (json_decode($this->configFile) === Null)
				/* salimos con una excepcion */
				$this->errorFormato();
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function procesarFichero() {
			/* obtenemos los datos del fichero */
			$this->obtenerDatosFichero();
			/* verificamos que el formato este correcto */
			$this->verificarFormatoArray();
			/* procesamos el fichero */
			$this->procesarDatosFichero();
		}

		private function obtenerDatosFichero() {
			/* mostramos un mensaje */
			$this->show('Obteniendo datos del fichero..', False);
			/* obtenemos los datos del fichero */
			$this->configFile = json_decode($this->configFile);
			/* convertimos el objeto a array */
			$this->configFile = MyTools::ObjectToArray($this->configFile);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function verificarFormatoArray() {
			/* mostramos un mensaje */
			$this->show('Verificando datos del fichero..', False);
			/* verificamos que el formato sea un array */
			if (gettype($this->configFile) !== 'array')
				/* salimos con una excepcion */
				$this->errorFormato();
			/* recorremos el array obtenido */
			foreach ($this->configFile AS $class => $value) {
				/* verificamos si el nombre de la clase no es un string */
				if (gettype($class) !== 'string')
					/* salimos con una excepcion */
					$this->errorFormato();
				/* verificamos que tenga la ruta al fichero */
				if (!isset($value['filePath']))
					/* salimos con una excepcion */
					$this->errorFormato('No se especfico la ruta de la clase ' . $this->classFormat($class));
			}
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function errorFormato($msg = False) {
			/* matamos el proceso con una excepcion */
			$this->newException('El formato del fichero no es correcto' . ($msg !== False ? ". $msg" : ''));
		}

		private function procesarDatosFichero() {
			/* mostramos un mensaje */
			$this->show('Procesando datos del fichero..');
			/* recorremos el array obtenido */
			foreach ($this->configFile AS $className => $classData) {
				/* realizamos verificaciones del fichero */
				$this->verificarFicheroClase($classData['filePath']);
				/* cargamos el fichero al atributo temporal */
				$this->cargarFicheroTemporal($classData['filePath']);
				/* verificamos si la clase existe en el fichero y si esta correctamente definida */
				$this->verificarClaseEnFichero($className, $classData);
				/* almacenamos el nombre de la clase y el fichero */
				$this->guardarClaseFichero($className, $classData);
			}
		}

		private function verificarFicheroClase($fileName) {
			/* mostramos un mensaje */
			$this->show('Verificando existencia del fichero ' . $this->fileFormat($fileName) . '..', False);
			/* verificamos si el fichero existe */
			if (!file_exists($fileName))
				/* salimos con una excepcion */
				$this->newException('El fichero no existe');
			/* verificamos si podemos leer el fichero */
			if (!is_readable($fileName))
				/* salimos con una excepcion */
				$this->newException('No se tienen permisos de lectura sobre el fichero');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function cargarFicheroTemporal($fileName) {
			/* mostramos un mensaje */
			$this->show('Obteniendo contenido del fichero ' . $this->fileFormat($fileName, True) . '..', False);
			/* cargamos en el atributo temporal el contenido del fichero */
			$this->tempClassData = preg_replace('/\s\s+/', ' ', file_get_contents($fileName));
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function verificarClaseEnFichero($className, $classData) {
			/* verificamos si la clase esta definida en el fichero */
			$this->verificarDefinicionClase($className, $classData['filePath'], (
				/* verificamos si es una interfaz */
				isset($classData['isInterface']) && $classData['isInterface'] == 'True' ?
				/* enviamos como parametro 'interfaz' */
				'interfaz' :
				/* enviamos como parametro 'clase' */
				'clase'
			));
			/* verificamos si la clase posee padre y esta definido */
			$this->verificarClasePadre($className, $classData);
			/* verificamos si la clase posee interfaces y si estan definidas */
			$this->verificarInterfaces($className, $classData);
		}

		private function verificarDefinicionClase($className, $filePath, $tipo = 'clase') {
			/* mostramos un mensaje */
			$this->show('Verificando existencia de la ' . $tipo . ' ' . $this->classFormat($className) . ' en el fichero ' . $this->fileFormat($filePath, True) . '..', False);
			/* verificamos si la clase esta definida en el fichero */
			if (!stristr($this->tempClassData, (
				/* verificamos si el tipo es una clase */
				$tipo == 'clase' ?
				/* verificamos con la palabra 'class' */
				"class $className" :
				/* verificamos con la palabra 'interface' */
				"interface $className {"
			)))
				/* salimos con una excepcion */
				$this->newException('La ' . $tipo . ' ' . $this->classFormat($className) . ' no esta definida en el fichero ' . $this->fileFormat($filePath));
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function verificarClasePadre($className, $classData) {
			/* mostramos un mensaje */
			$this->show('Verificando herencia de la clase ' . $this->classFormat($className) . '..', False);
			/* verificamos si tiene padre */
			if (isset($classData['parent'])) {
				/* verificamos si en el fichero esta definido el padre */
				if (!stristr($this->tempClassData, "class $className extends {$classData['parent']}"))
					/* salimos con una excepcion */
					$this->newException('La clase no tiene definido como padre la clase ' . $this->classFormat($classData['parent']));
			/* si no se especifico padre, verificamos si en el fichero la clase tiene padre */
			} elseif (stristr($this->tempClassData, "class $className extends"))
				/* salimos con una excepcion */
				$this->newException('La clase ' . $this->classFormat($className) . ' tiene definida una clase padre que no se especifico en el fichero de configuracion');
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function verificarInterfaces($className, $classData) {
			/* mostramos un mensaje */
			$this->show('Verificando interfaces de la clase ' . $this->classFormat($className) . '..', False);
			/* verificamos si tiene interfaces */
			if (isset($classData['interfaces'])) {
				/* verificamos si tiene padre */
				if (isset($classData['parent']))
					/* verificamos las interfaces con el padre */
					$this->combinacionInterfaces($className, $classData['interfaces'], $classData['parent']);
				else
					/* verificamos las interfaces sin padre */
					$this->combinacionInterfaces($className, $classData['interfaces']);
			} else {
				/* verificamos si utiliza alguna interfaz */
				if (
					/* verificamos utilizando padre */
					(isset($classData['parent']) && stristr($this->tempClassData, "class $className extends {$classData['parent']} implements")) ||
					/* verificamos sin padre */
					(stristr($this->tempClassData, "class $className implements"))
				)
					/* salimos con una excepcion */
					$this->newException('La clase tiene definida una o mas interfaces que no se especificaron en el fichero de configuracion');
			}
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function combinacionInterfaces($className, $classInterfaces, $classParent = False) {
			/* armamos las posibles combinaciones */
			$combinaciones = MyTools::getCombinations($classInterfaces);
			/* formateamos las combinaciones */
			$combinaciones = $this->formatearCombinaciones($combinaciones);
			/* verificamos las interfaces */
			$this->verificarInterfacesCombinaciones($className, $combinaciones, $classParent);
		}

		private function formatearCombinaciones($combinaciones) {
			/* creamos el array resultante */
			$resultado = Array();
			/* recorremos las combinaciones */
			foreach ($combinaciones AS $combinacion) {
				/* creamos la cadena */
				$cadena = '';
				/* recorremos las interfaces */
				foreach ($combinacion AS $interfaz) {
					/* verificamos si agregamos la coma */
					if ($cadena !== '')
						/* agregamos la coma */
						$cadena .= ', ';
					/* agregamos la interfaz */
					$cadena .= $interfaz;
				}
				/* agregamos la cadena al resultado */
				$resultado[] = 'implements ' . $cadena;
			}
			/* retornamos el array con las cadenas */
			return $resultado;
		}

		private function verificarInterfacesCombinaciones($className, $combinaciones, $classParent) {
			/* creamos una variable de resultado */
			$resultado = False;
			/* recorremos las combinaciones */
			foreach ($combinaciones AS $implements) {
				/* verificamos si la clase tiene padre y si posee las interfaces */
				if ($classParent === False && stristr($this->tempClassData, "class $className $implements {")) {
					/* modificamos el resultado */
					$resultado = True;
					/* finalizamos el buqle */
					break;
				/* verificamos si la clase posee las interfaces */
				} elseif (stristr($this->tempClassData, "class $className extends $classParent $implements {")) {
					/* modificamos el resultado */
					$resultado = True;
					/* finalizamos el buqle */
					break;
				}
			}
			/* verificamos como salio el resultado */
			if ($resultado === False)
				/* salimos con una excepsion */
				$this->newException('La clase no tiene definida alguna interfaz especificada en el fichero de configuracion o no se especifico una interfaz que esta la utiliza');
		}

		private function guardarClaseFichero($className, $classData) {
			/* agregamos un elemento al array con la clase y sus datos */
			$this->fileClasses[$className] = $classData;
		}

		private function classFormat($className = False) {
			/* retornamos el nombre de la clase con formato */
			return $this->italic($this->bold($this->blue($className)));
		}

		private function fileFormat($fileName = False, $onlyBase = False) {
			/* retornamos el nombre del fichero */
			return $this->italic($this->yellow(
				/* verificamos si es solo el nombre del fichero */
				$onlyBase === False ?
				/* enviamos la ruta completa */
				$fileName :
				/* enviamos solo el nombre del fichero */
				basename($fileName)
			));
		}
	}
?>